//
//  FXDragGestureRecognizer.swift
//  FXDragGestureRecognizer
//
//  Created by fanxiaoxin_1987@126.com on 11/22/2021.
//  Copyright (c) 2021 fanxiaoxin_1987@126.com. All rights reserved.
//

import UIKit

/// 诸多限制无法直接继承自UIGestureRecognizer来实现，使用时需要使用self.gestureRecognizer去添加到对应的View
open class FXDragGestureRecognizer: UIPanGestureRecognizer {
    /// 初始拖动方向(Began触发时的拖动方向)
    open var direction: Direction {
        return self._panHandle.direction
    }
    /// 当前拖动方向(手动过程中反方向拖时会切换为新的方向，如左换右，但左换上下这种不会切换)
    open var currentDirection: Direction {
        let offset = self.offset(in: self.view)
        if offset > 0 {
            return self.direction
        } else if offset < 0 {
            switch self.direction {
            case .up: return .down
            case .down: return .up
            case .left: return .right
            case .right: return .left
            case .unknow: return .unknow
            }
        } else {
            return .unknow
        }
    }
    /// 用来判断水平滑动方向的阀值，默认20
    open var gestureMinimumTranslationX: CGFloat {
        get { return self._panHandle.gestureMinimumTranslationX }
        set { self._panHandle.gestureMinimumTranslationX = newValue}
    }
    /// 用来判断竖直滑动方向的阀值，默认10
    open var gestureMinimumTranslationY: CGFloat {
        get { return self._panHandle.gestureMinimumTranslationY }
        set { self._panHandle.gestureMinimumTranslationY = newValue}
    }
    /// 需要使用该状态来判断
    open var dragState: UIGestureRecognizerState {
        get {
            return self._panHandle.dragState
        }
        set {
            self._panHandle.dragState = newValue
        }
    }
    /// 初始方向相对该View的偏移量，正向为正数，反向则为负数
    open func offset(in view: UIView?) -> CGFloat {
        let translation = super.translation(in: view)
        switch self.direction {
        case .up: return -translation.y
        case .down: return translation.y
        case .left: return -translation.x
        case .right: return translation.x
        case .unknow: return 0
        }
    }
    /// 当前方向相对该View的偏移量，永远为正数
    open func currentOffset(in view: UIView?) -> CGFloat {
        let translation = super.translation(in: view)
        switch self.currentDirection {
        case .up: return -translation.y
        case .down: return translation.y
        case .left: return -translation.x
        case .right: return translation.x
        case .unknow: return 0
        }
    }
    
    private let _panHandle: PanHandle
    private var targets: [(AnyObject, Selector)] = []
    public override init(target: Any?, action: Selector?) {
        let handle = PanHandle()
        self._panHandle = handle
        super.init(target: handle, action: #selector(handle.handlePanGesture))
        handle.action =  { [weak self] in
            self?.sendActions()
        }
        if let target = target as AnyObject?, let action = action {
            self.targets.append((target, action))
        }
    }
    open override func addTarget(_ target: Any, action: Selector) {
        if let target = target as AnyObject? {
            self.targets.append((target, action))
        }
    }
    open override func removeTarget(_ target: Any?, action: Selector?) {
        self.targets.removeAll { t, a in
            if let target = target as AnyObject? {
                if target !== t {
                    return false
                }
            }
            if let action = action {
                if action != a {
                    return false
                }
            }
            return true
        }
    }
    open func sendActions() {
        self.targets.forEach { t, a in
            self.sendAction(for: t, selector: a)
        }
    }
    open func sendAction(for target: AnyObject, selector: Selector) {
        if let method = class_getInstanceMethod(type(of: target), selector) {
            let imp = method_getImplementation(method)
            typealias Function = @convention(c) (AnyObject, Selector, FXDragGestureRecognizer) -> Void
            let function = unsafeBitCast(imp, to: Function.self)
            function(target, selector, self)
        }
    }
}
extension FXDragGestureRecognizer {
    public enum Direction {
        /// 上
        case up
        /// 左
        case left
        /// 下
        case down
        /// 右
        case right
        /// 未识别
        case unknow
    }
}
extension FXDragGestureRecognizer {
    class PanHandle {
        /// 当前手势方向
        private(set) var direction: Direction = .unknow
        /// 用来判断水平滑动方向的阀值
        var gestureMinimumTranslationX: CGFloat = 20
        /// 用来判断竖直滑动方向的阀值
        var gestureMinimumTranslationY: CGFloat = 10
        /// 拖动状态
        var dragState: UIGestureRecognizerState = .possible
        
        var action: (() -> Void)?
        
        @objc func handlePanGesture(_ gr: FXDragGestureRecognizer) {
            let translation = gr.translation(in: gr.view)
            self.dragState = gr.state
            switch gr.state {
            case .began:
                self.direction = .unknow
            case .changed:
                if self.direction == .unknow {
                    self.direction = self.determineDragDirection(translation)
                    if self.direction != .unknow {
                        self.dragState = .began
                    }
                }
                if self.direction != .unknow {
                    self.action?()
                }
            case .ended, .cancelled, .failed:
                if self.direction != .unknow {
                    self.action?()
                    self.direction = .unknow
                }
            default: break
            }
        }
        private func determineDragDirection(_ translation: CGPoint) -> Direction {
            if abs(translation.x) > self.gestureMinimumTranslationX {
                let gestureHorizontal: Bool
                if translation.y == 0.0 {
                    gestureHorizontal = true
                } else {
                    gestureHorizontal = abs(translation.x / translation.y) > 5.0
                }
                if gestureHorizontal {
                    return translation.x > 0.0 ? .right : .left
                }
            } else if abs(translation.y) > self.gestureMinimumTranslationY {
                let gestureVertical: Bool
                if translation.x == 0.0 {
                    gestureVertical = true
                } else {
                    gestureVertical = abs(translation.y / translation.x) > 5.0
                }
                if gestureVertical {
                    return translation.y > 0.0 ? .down : .up
                }
            }
            return .unknow
        }
    }
}
